﻿#ifdef WIN32

#include "Network/UDPServer.h"
#include "WIndowsSocketCommon.h"
#include <memory>
#include <cassert>
#include "Async/PackagedTask.h"

#include "Output/Console.h"
#include "Containers/Tuple.h"

#include <WinSock2.h>

TThreadSafeSharedPtr<CUDPServer> CUDPServer::Create(const SIPAddress& IP, uint16_t Port)
{
    CWindowsSocketContext::Get(); //make sure WSAStartup is called

    void* PlatformSocket = (void*)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    assert((SOCKET)PlatformSocket != INVALID_SOCKET && "socket failed");

    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(Port);
    if (IP.IsIPv6())
    {
        memcpy(&addr.sin_addr, IP.IP.IPv6, sizeof(IP.IP.IPv6));
    }
    else
    {
        memcpy(&addr.sin_addr, IP.IP.IPv4, sizeof(IP.IP.IPv4));
    }
    int32_t Result = bind((SOCKET)PlatformSocket, (sockaddr*)&addr, sizeof(addr));

    if (Result == SOCKET_ERROR)
    {
        //connnect failed
        WH::PrintLine(U"UDP bind to :" + IP.ToString() + U" Port" + Port + U" failed");
        return nullptr;
    }

    return WH::MakeThreadSafeSharedPtr<CUDPServer>(PlatformSocket);

}

CUDPServer::CUDPServer(void* InPlatformSocket)
{
    PlatformSocket = InPlatformSocket;

    //set non blocking
    {
        u_long NonBlocking = 1;
        ioctlsocket((SOCKET)PlatformSocket, FIONBIO, &NonBlocking);
    }

    StartReciveTask();
}

CUDPServer::~CUDPServer()
{
    closesocket((SOCKET)PlatformSocket);
}


void CUDPServer::StartReciveTask()
{
    using ReceivedType=TTuple<TVector<uint8_t> /*recived data*/ ,SIPAddress /*IP*/, uint16_t /*Port*/>;

    std::shared_ptr<CPackagedTask<ReceivedType>> ReciveTask= 
        std::make_shared<CPackagedTask<ReceivedType>>(
        CAsyncSystem::Get()->ThreadPool
        ,[this](bool&) ->ReceivedType
        {
            static const uint16_t ReciveBufferSize = 1024;

            TVector<uint8_t> Data;
            Data.Resize(ReciveBufferSize); 

            sockaddr_in addr;
            int32_t addrSize = sizeof(addr);

            while(true)
            {
                int32_t RecivedSize = recvfrom((SOCKET)PlatformSocket, (char*)Data.Data(), ReciveBufferSize, 0, (sockaddr*)&addr, &addrSize);

                if(RecivedSize==0)
                {
                    //connection closed
                    return ReceivedType();
                }

                if(RecivedSize==SOCKET_ERROR)
                {
                    int32_t Error = WSAGetLastError();

                    switch(Error)
                    {
                        case WSAEWOULDBLOCK:
                        {
                            //no data
                            return ReceivedType();
                        }

                        case WSAEMSGSIZE:
                        {
                            //buffer too small
                            Data.Resize(Data.Num() * 2);
                            continue;
                        }

                        default:
                        {
                            //error
                            return ReceivedType();
                        }
                    }
                }
                else
                {
                    uint8_t IP[4];
                    memcpy(IP, &addr.sin_addr, sizeof(IP));

                    return WH::MakeTuple(Data, SIPAddress(IP[0],IP[1],IP[2],IP[3]), ntohs(addr.sin_port));
                }

            }

            assert(false && "unreachable");
            return ReceivedType();

        }
    );

    ReciveTask->ThenInMainThread<void>(
        [this,ReciveTask](bool&)
        {
            ReceivedType& InReceived=ReciveTask->Task->GetResult();
            if(InReceived.First.Num()>0)
            {
                this->OnDataReceived.ExecuteIfBound(InReceived.First, InReceived.Second, InReceived.Third);
            }

            this->StartReciveTask();
        }
    );
    
    ReciveTask->Start();
}

#endif